
#pragma once

#include "../ql/qleval.h"

static Obj *
rp_function_quit( void * tag, List * args )
{
    EvalContext *   e = tag;
    evaluator_emit_error( e, QL_ERR_STOP );
    return 0;
}

static Obj *
rp_function_plus( void * tag, List * args )
{
    EvalContext *   e = tag;
    AllocContext *  allocator = get_evaluator_allocator( e );
    IntNum *        arg1;
    IntNum *        arg2;

    if( list_length(args) != 2 )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    arg1 = (IntNum *)list_at( args, 0 );
    arg2 = (IntNum *)list_at( args, 1 );

    if( arg1->type != OBJ_INT_NUM || arg2->type != OBJ_INT_NUM )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    return (Obj *)alloc_int_num( allocator, arg1->val + arg2->val  );
}

static Obj * rp_function_typeof( void * tag, List * args )
{
    EvalContext *   e = tag;
    const char *    type_name;
    AllocContext *  allocator = get_evaluator_allocator( e );
    
    if( list_length(args) != 1 )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    switch( args->car->type )
    {
    case OBJ_NIL:
        type_name = "nil";
        break;

    case OBJ_T:
        type_name = "t";
        break;

    case OBJ_ID:
        type_name = "id";
        break;

    case OBJ_INT_NUM:
        type_name = "int";
        break;

    case OBJ_DBL_NUM:
        type_name = "double";
        break;

    case OBJ_STRING:
        type_name = "string";
        break;

    case OBJ_LIST:
        type_name = "list";
        break;

    default:
        evaluator_emit_error( e, QL_ERR_FAIL );
        return 0;
    }

    return (Obj *)alloc_id( allocator, type_name, alloc_id(allocator, "type", 0) );
}

static Obj * rp_function_cmp( void * tag, List * args )
{
    EvalContext *   e = tag;
    AllocContext *  allocator = get_evaluator_allocator( e );

    if( list_length(args) != 2 )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    return (Obj *)alloc_int_num(
        allocator,
        objcmp(list_at(args, 0), list_at(args, 1))
        );
}

static Obj * rp_function_print( void * tag, List * args )
{
    List *  l = args;
    for( ; l != 0; l = l->cdr )
    {
        print_obj( l->car, stderr );
        fputs( " ", stderr );
    }
    fputs( "\n", stderr );

    return g_t_obj;
}

static Obj * rp_function_get( void * tag, List * args )
{
    EvalContext *   e = tag;
    Id *            keyword;
    List *          list;
    Obj *           result;

    if( list_length(args) != 2 )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    keyword = (Id *)args->car;
    list = (List *)args->cdr->car;

    if( OBJ_ID != keyword->type || OBJ_LIST != list->type )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    result = get_list_elem_by_keyword( list, keyword );

    return 0 == result ? g_nil_obj : result;
}

static Obj * rp_function_dump_mem_state( void * tag, List * args )
{
    EvalContext *   e = tag;
    if( list_length(args) != 0 )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }

    dump_mem_state( get_evaluator_allocator(e), stderr );

    return g_t_obj;
}

static void set_default_functions( EvalContext * eval )
{
    AllocContext *  c = get_evaluator_allocator( eval );
    add_function( eval, alloc_id(c, "quit", 0), rp_function_quit );
    add_function( eval, alloc_id(c, "q", 0), rp_function_quit );
    add_function( eval, alloc_id(c, "typeof", 0), rp_function_typeof );
    add_function( eval, alloc_id(c, "cmp", 0), rp_function_cmp );
    add_function2( eval, alloc_id(c, "print", 0), rp_function_print, true, eval );
    add_function( eval, alloc_id(c, "get", 0), rp_function_get );
    add_function( eval, alloc_id(c, "mem", 0), rp_function_dump_mem_state );

    // this will makes such function work: (+ 12 (+ 34 78))
    add_function2( eval, alloc_id(c, "+", 0), rp_function_plus, true, eval );

    // this will not make the same work: (+ 12 (+ 34 78)), because second argument in
    // this call will not be evaluated
    //add_function( eval, alloc_id(c, "+", 0), rp_function_plus );
}

static void test_eval( AllocContext * ac )
{
    EvalContext *   eval = create_evaluator( ac );

    set_default_functions( eval );

    launch_evaluator_loop_from_stdin( eval );

    free_evaluator( eval );
}
